-- Config
local DEBUG = false
local SPOTTHROTTLE = 30 -- in seconds

-- Define Addon
APB = Rock:NewAddon("AllPointsBulletin", "LibRockEvent-1.0", "LibRockConsole-1.0", "LibRockDB-1.0", "LibRockConfig-1.0", "LibRockComm-1.0", "LibParser-4.0")
local APB = APB
APB.revision = 1--tonumber(string.sub("$Rev: 00000 $", 12 -3))
APB.version = "r" .. APB.revision
APB.date = "2007-12-03"--string.sub("$Date: 2007-12-03 00:00:00 -0500 (Mon, 03 Dec 2007) $", 8, 17)

-- Setup Resources
local L = Rock("LibRockLocale-1.0"):GetTranslationNamespace("APB")
APB.L = L
local Crayon = Rock("LibCrayon-3.0")
local BZ = Rock("LibBabble-Zone-3.0")
local BZL = BZ:GetLookupTable()
local BZH = BZ:GetUnstrictLookupTable()
local BZR = BZ:GetReverseLookupTable()
local BC = Rock("Babble-Class-2.2") -- Want a LibRock version for this!
local BR = Rock("Babble-Race-2.2") -- Want a LibRock version for this!

-- Configure Translations
L:AddTranslations("enUS", function() return {
	["Addon to track enemies, police scanner style."] = true,
	["Currently watching for"] = true,
	["Added"] = true,
	["Removed"] = true,
	["to the watch list"] = true,
	["from the watch list"] = true,
	["is not on the watch list"] = true,
	["Spotted"] = true,
	["Unknown"] = true,
	["Last Seen"] = true,
	["Refresh"] = true,
	["APB"] = true,
} end) -- English by mattswell - 2007-12-24

-- Configure Database
APB:SetDatabase("AllPointsBulletinDB")
function APB:AquireDBNamespace(...)
	return self:GetDatabaseNamespace(...)
end
function APB:RegisterDefaults(...)
	return self:SetDatabaseNamespaceDefaults(...)
end

-- Configure Options (Rock)
APB.options = {
	type = "group",
	name = "APB",
	desc = L["Addon to track enemies, police scanner style."],
}

-- UI Setup
UIPanelWindows["AllPointsBulletinFrame"] = { area = "left", pushable = 0, whileDead = 1 }
APBFRAME_SUBFRAMES = { "SpottedFrame" }
SPOTTED_TO_DISPLAY = 20
APB_FRAME_SPOTTED_HEIGHT = 20

SORT_METHODS = {
	name = function(a, b) if (APB.spottedListSort.dir == 1) then
			return a > b
			else return a < b
		end end,
	race = function(a, b) if (APB.spottedListSort.dir == 1) then
			return BR[APB.spottedList[a][2]] > BR[APB.spottedList[b][2]]
			else return BR[APB.spottedList[a][2]] < BR[APB.spottedList[b][2]]
		end end,
	class = function(a, b) if (APB.spottedListSort.dir == 1) then
			return BC[APB.spottedList[a][3]] > BC[APB.spottedList[b][3]]
			else return BC[APB.spottedList[a][3]] < BC[APB.spottedList[b][3]]
		end end,
	level = function(a, b) if (APB.spottedListSort.dir == 1) then
			return APB:RightLevel(APB.spottedList[a][4]) > APB:RightLevel(APB.spottedList[b][4])
			else return APB:RightLevel(APB.spottedList[a][4]) < APB:RightLevel(APB.spottedList[b][4])
		end end,
	guild = function(a, b) if (APB.spottedListSort.dir == 1) then
			return APB.spottedList[a][5] > APB.spottedList[b][5]
			else return APB.spottedList[a][5] < APB.spottedList[b][5]
		end end,
	zone = function(a, b) if (APB.spottedListSort.dir == 1) then
			return BZL[APB.spottedList[a][6]] > BZL[APB.spottedList[b][6]]
			else return BZL[APB.spottedList[a][6]] < BZL[APB.spottedList[b][6]]
		end end,
	time = function(a, b) if (APB.spottedListSort.dir == 1) then
			return APB.spottedList[a][10] < APB.spottedList[b][10]
			else return APB.spottedList[a][10] > APB.spottedList[b][10]
		end end,
}

-- Event-OnInitialize
function APB:OnInitialize()
	self:SetConfigTable(self.options)
	--self:SetConfigSlashCommand("/APB")
	self:AddSlashCommand("OnSlashCommand", "/APB")
	self.options.extraArgs.active = nil
	
	-- Comm Options
	self:SetCommPrefix("APB")
	local memoizations = {}
	for zone in BZ:Iterate() do -- zone names
		table.insert(memoizations, zone)
	end
	for class in BC:GetIterator() do -- class names
		table.insert(memoizations, class)
	end
	for race in BR:GetIterator() do -- race names
		table.insert(memoizations, race)
	end
	self:AddMemoizations(memoizations)
	memoizations = nil
	
	-- Set Defaults
	self:SetDatabaseDefaults("profile", {
		commActive = true,
		numEncounter = 20,
		numSpotted = 200,
	})
	self:SetDatabaseDefaults("server", {
		watchList = {},
	})
	
	self.spottedList = {}
	self.spottedListSort = { dir = 0, keys = {}, method = "time" }
	
	self.EncounterList = {}
	self.EncounterListSort = { dir = 0, keys = {}, method = "time" }
	
	self.lastAttacker = nil
	
	-- GUI
	self:LocalizeGui()
end

-- Event-OnEnable
function APB:OnEnable()
	-- Targeting Events
	self:AddEventListener("UNIT_TARGET")
	self:AddEventListener("UNIT_FACTION")
	self:AddEventListener("UPDATE_MOUSEOVER_UNIT")
	
	-- Comm Events
	self:AddCommListener(self.commPrefix, "GUILD")
	self:AddCommListener(self.commPrefix, "ZONE")
	
	-- Combat Events
	self:AddParserListener({
	    eventType = "Damage",
	    recipientID = "player",
	}, "OnPlayerDamaged")
	self:AddParserListener({
	    eventType = "Death",
	    recipientID = "player",
	}, "OnPlayerDeath")
end

-- Event-OnSlashCommand
function APB:OnSlashCommand(cmd, args)
	local argv = {}
	argv = (" "):split(args)
	local argc = #argv
	assert(cmd == "/APB")
	if argv[1] == "watch" then
		if argv[2] == "list" then
			-- display watch list
			self:WatchListPrint()
		elseif argv[2] == "add" and argc > 2 then
			-- add player to watch list 	
			self:WatchListAdd(argv[3], table.concat(argv, " ", 4))
		elseif argv[2] == "remove" and argc > 2 then
			-- remove player from watch list
			self:WatchListRemove(argv[3])
		end
	else
		self:ShowHideFrame()
	end
end

-- Event-OnCommReceive
function APB:OnCommReceive(prefix, distribution, sender, name, race, class, level, guild, zone, zoneMini, locX, locY)
	if sender ~= UnitName("player") then
		self:DPrint(string.format("Received information on %s from %s!", name, sender))
		self:AddSpottedUnit(sender, name, race, class, level, guild, zone, zoneMini, locX, locY)
	end
end

function APB:UNIT_TARGET(ns, event, unit)
	APB:ReportSpottedUnit(unit.."target", unit)
end

function APB:UNIT_FACTION(ns, event, unit)
	APB:ReportSpottedUnit(unit)
end

function APB:UPDATE_MOUSEOVER_UNIT()
	APB:ReportSpottedUnit("mouseover", "player")
end

function APB:OnPlayerDamaged(t)
	self.lastAttacker = t.sourceName
	self:DPrint(string.format("Recorded damage from %s.", t.sourceName))
end

function APB:OnPlayerDeath(t)
	self:DPrint(string.format("Recorded death to %s (maybe?).", self.lastAttacker))
end

function APB:ReportSpottedUnit(unit, reporter)
	if not self:IsValidEnemy(unit) then return end
	local name, race, class, level, guild, zone, zoneMini, locX, locY
	name = UnitName(unit)
	race = BR:GetReverseTranslation(UnitRace(unit))
	class = BC:GetReverseTranslation(UnitClass(unit))
	level = UnitLevel(unit)
	if level == -1 then level = (UnitLevel("player") + 10).."+" end
	guild = GetGuildInfo(unit)
	if guild == nil then guild = "" end
	zone = BZR[GetRealZoneText()]
	zoneMini = BZR[GetMinimapZoneText()]
	
	locX, locY = GetPlayerMapPosition("player")
	locX = math.floor(locX * 10000) / 100
	locY = math.floor(locY * 10000) / 100
	
	if not self:SpottedThrottle(name) then
		self:SendCommMessage("GUILD", name, race, class, level, guild, zone, zoneMini, locX, locY)
		self:SendCommMessage("ZONE", name, race, class, level, guild, zone, zoneMini, locX, locY)
	end
	
	self:AddSpottedUnit(UnitName("player"), name, race, class, level, guild, zone, zoneMini, locX, locY)
	self:AddEncounteredUnit(name)
end

function APB:AddSpottedUnit(cop, name, race, class, level, guild, zone, zoneMini, locX, locY)
	if cop == nil or name == nil or race == nil or class == nil or level == nil or zone == nil or locX == nil or locY == nil then
		if name ~= nil then
			if cop == nil then self:DPrint("Don't know cop for "..name) end
			if race == nil then self:DPrint("Don't know race for "..name) end
			if class == nil then self:DPrint("Don't know class for "..name) end
			if level == nil then self:DPrint("Don't know level for "..name) end
			if guild == nil then self:DPrint("Don't know guild for "..name) end
			if zone == nil then self:DPrint("Don't know zone for "..name) end
			if locX == nil then self:DPrint("Don't know locX for "..name) end
			if locY == nil then self:DPrint("Don't know locY for "..name) end
			self:DPrint("Not enough information on "..name)
		else
			self:DPrint("Not enough information on unknown!?")
		end
		return
	end
	if self.spottedList[name] == nil then
		self.spottedList[name] = { cop, race, class, level, guild, zone, zoneMini, locX, locY, GetTime() }
		self:DPrint(string.format("Added %s. Reported by %s.", name, cop))
	else
		self.spottedList[name][1] = cop
		self.spottedList[name][2] = race
		self.spottedList[name][3] = class
		self.spottedList[name][4] = level
		self.spottedList[name][5] = guild
		self.spottedList[name][6] = zone
		self.spottedList[name][7] = zoneMini
		self.spottedList[name][8] = locX
		self.spottedList[name][9] = locY
		self.spottedList[name][10] = GetTime()
		self:DPrint(string.format("Updated %s. Reported by %s.", name, cop))
	end
	--SortSpotted()	--implies an update as well
end

function APB:AddEncounteredUnit(name)
	local pos = 0
	for index, value in pairs(self.EncounterList) do
		if value == name then pos = index end
	end
	if pos > 0 then table.remove(self.EncounterList, pos) end
	table.insert(self.EncounterList, 1, name)
end

function APB:IsValidEnemy(unit)
	if UnitIsVisible(unit) ~= 1 then return false end
	if UnitName(unit) == nil or UnitRace(unit) == nil or UnitLevel(unit) == nil or UnitClass(unit) == nil or UnitFactionGroup(unit) == nil then return false end
	if UnitIsPlayer(unit) ~= 1 or IsInInstance() == 1 or UnitFactionGroup("player") == UnitFactionGroup(unit) then return false end
	return true
end

function APB:SpottedThrottle(name)
	if self.spottedList[name] == nil then return false end
	local timeSince = GetTime() - self.spottedList[name][10]
	if timeSince > SPOTTHROTTLE then return false end
	return true
end

function APB:RefreshKeys()
	APB.spottedListSort.keys = {}
	for k, v in pairs(APB.spottedList) do table.insert(APB.spottedListSort.keys, k) end
end

-- Watch List Functions

-- Print Watch List
function APB:WatchListPrint()
	self:Print(string.format("%s:", L["Currently watching for"]))
	for player, reason in pairs(self.db.server.watchList) do
		if reason ~= "" then
			self:Print(string.format("%s (%s)", player, reason))
		else
			self:Print(player)
		end
	end
end

-- Add Player to Watch List
function APB:WatchListAdd(player, reason)
	self.db.server.watchList[player] = reason
	self:Print(string.format("%s %s %s.", L["Added"], player, L["to the watch list"]))
end

-- Remove Player from Watch List
function APB:WatchListRemove(player)
	if self.db.server.watchList[player] then
		self.db.server.watchList[player] = nil
		self:Print(string.format("%s %s %s.", L["Removed"], player, L["from the watch list"]))
	else
		self:Print(string.format("%s %s.", player, L["is not on the watch list"]))
	end
end

-- Localize the Gui
function APB:LocalizeGui()
	AllPointsBulletinFrameTab1:SetText(L["Spotted"])
end

-- Show/Hide GUI Frame
function APB:ShowHideFrame()
    if AllPointsBulletinFrame:IsVisible() then
		HideUIPanel(AllPointsBulletinFrame)
	else
		ShowUIPanel(AllPointsBulletinFrame)
	end
end

-- Debug msg printing
function APB:DPrint(msg)
	if DEBUG then self:Print(string.format("[DEBUG] %s", msg)) end
end

function APB:RightLevel(level)
	rlvl, _ = string.gsub(level, "+", "")
	return tonumber(rlvl)
end

---- [ GUI XML FUNCTIONS ] ----

function AllPointsBulletinFrame_ShowSubFrame(frameName)
	for index, value in pairs(APBFRAME_SUBFRAMES) do
		if value == frameName then
			getglobal(value):Show()
		else
			getglobal(value):Hide()
		end	
	end 
end

function AllPointsBulletinFrame_OnLoad()
	PanelTemplates_SetNumTabs(this, 1)
	AllPointsBulletinFrame.selectedTab = 1
	PanelTemplates_UpdateTabs(this)
end

function AllPointsBulletinFrame_OnShow()
	APB:RefreshKeys()
	AllPointsBulletinFrame_Update()
	PlaySound("igCharacterInfoTab")
end

function AllPointsBulletinFrame_Update()
	if AllPointsBulletinFrame.selectedTab == 1 then
		AllPointsBulletinFrameTopLeft:SetTexture("Interface\\ClassTrainerFrame\\UI-ClassTrainer-TopLeft")
		AllPointsBulletinFrameTopRight:SetTexture("Interface\\ClassTrainerFrame\\UI-ClassTrainer-TopRight")
		AllPointsBulletinFrameTop:SetTexture("Interface\\AddOns\\APB\\Art\\UI-ClassTrainer-Top")
		AllPointsBulletinFrameBottomLeft:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-General-BottomLeft")
		AllPointsBulletinFrameBottom:SetTexture("Interface\\AddOns\\APB\\Art\\UI-Character-General-Bottom")
		AllPointsBulletinFrameBottomRight:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-General-BottomRight")
		AllPointsBulletinFrameTitleText:SetText(L["APB"]..": "..L["Spotted"])
		SpottedFrameColumnHeader7:SetText(L["Last Seen"])
		SpottedFrameRefreshButton:SetText(L["Refresh"])
		AllPointsBulletinFrame_ShowSubFrame("SpottedFrame")
		SpottedList_Update()
	end
end

function AllPointsBulletinFrame_OnHide()
	PlaySound("igMainMenuClose")
	for index, value in pairs(FRIENDSFRAME_SUBFRAMES) do
		getglobal(value):Hide()
	end
end

function AllPointsBulletinFrameSpottedButton_OnClick(button)
	if ( button == "LeftButton" ) then
		SpottedFrame.selectedUnit = getglobal("SpottedFrameButton"..this:GetID()).spottedIndex
		SpottedList_Update()
	else
		--local name = getglobal("WhoFrameButton"..this:GetID().."Name"):GetText()
		--FriendsFrame_ShowDropdown(name, 1)
	end
end

function SpottedFrameColumn_SetWidth(width, frame)
	if not frame then
		frame = this
	end
	frame:SetWidth(width)
	getglobal(frame:GetName().."Middle"):SetWidth(width - 9)
end

function SpottedList_Update()
	local numSpotted = table.sizeOf(APB.spottedListSort.keys)
	local button, buttonText
	local spottedUnit, r, g, b
	local spottedOffset = FauxScrollFrame_GetOffset(SpottedListScrollFrame)
	local spottedIndex
	local showScrollBar = nil
	local lastseen, minutes, seconds
	local name, cop, race, class, level, guild, zone, zoneMini, locX, locY, time
	if ( numSpotted > SPOTTED_TO_DISPLAY ) then
		showScrollBar = 1
	end
	for i=1, SPOTTED_TO_DISPLAY, 1 do
		spottedIndex = spottedOffset + i
		button = getglobal("SpottedFrameButton"..i)
		button.spottedIndex = spottedIndex
		if spottedIndex > numSpotted then
			button:Hide()
		else
			button:Show()
			spottedUnit = APB.spottedList[APB.spottedListSort.keys[spottedIndex]]
			
			name = APB.spottedListSort.keys[spottedIndex]
			cop = spottedUnit[1]
			race = spottedUnit[2]
			class = spottedUnit[3]
			level = spottedUnit[4]
			guild = spottedUnit[5]
			zone = spottedUnit[6]
			zoneMini = spottedUnit[7]
			locX = spottedUnit[8]
			locY = spottedUnit[9]
			time = spottedUnit[10]
			
			r, g, b = BC:GetColor(class)
			
			days = math.floor((GetTime() - time) / 86400)
			hours = math.floor((GetTime() - time) % 86400 / 3600)
			minutes = math.floor((GetTime() - time) % 3600 / 60)
			seconds = math.ceil((GetTime() - time) % 60)
			
			lastseen = ""
			if seconds > 0 and minutes < 5 then lastseen = string.format("%ds", seconds)..lastseen end
			if minutes > 0 and hours < 3 then lastseen = string.format("%dm", minutes)..lastseen end
			if hours > 0 and days < 1 then lastseen = string.format("%dh", hours)..lastseen end
			if days > 0 then lastseen = string.format("%dd", days) end
			
			buttonText = getglobal("SpottedFrameButton"..i.."Level")
			buttonText:SetText(level)
			buttonText = getglobal("SpottedFrameButton"..i.."Name")
			buttonText:SetText(name)
			buttonText = getglobal("SpottedFrameButton"..i.."Race")
			buttonText:SetText(BR[race])
			buttonText = getglobal("SpottedFrameButton"..i.."Class")
			buttonText:SetText(BC[class])
			buttonText:SetTextColor(r, g, b)
			buttonText = getglobal("SpottedFrameButton"..i.."Zone")
			buttonText:SetText(BZL[zone])
			buttonText = getglobal("SpottedFrameButton"..i.."LastSeen")
			buttonText:SetText(lastseen)
			local variableText = getglobal("SpottedFrameButton"..i.."Guild")
			variableText:SetText(guild)
		
			-- If need scrollbar resize columns
			if showScrollBar then
				variableText:SetWidth(130)
			else
				variableText:SetWidth(150)
			end
			
			-- Highlight the correct who
			if SpottedFrame.selectedUnit == spottedIndex then
				button:LockHighlight()
			else
				button:UnlockHighlight()
			end
		end
	end

	-- If need scrollbar resize columns
	if showScrollBar then
		SpottedFrameColumn_SetWidth(138, SpottedFrameColumnHeader5)
	else
		SpottedFrameColumn_SetWidth(158, SpottedFrameColumnHeader5)
	end

	-- ScrollFrame update
	FauxScrollFrame_Update(SpottedListScrollFrame, numSpotted, SPOTTED_TO_DISPLAY, APB_FRAME_SPOTTED_HEIGHT )

	PanelTemplates_SetTab(AllPointsBulletinFrame, 1)
	--ShowUIPanel(AllPointsBulletinFrame)
end

function SortSpotted(method)
	local numrows = table.sizeOf(APB.spottedList)
	if (method ~= nil) then
		if method == APB.spottedListSort.method then
			APB.spottedListSort.dir = (APB.spottedListSort.dir + 1) % 2
		else
			APB.spottedListSort.method = method
			APB.spottedListSort.dir = 0
		end
	end
	if SORT_METHODS[APB.spottedListSort.method] == nil then
		APB:DPrint(APB.spottedListSort.method.." is nil?")
		APB.spottedListSort.method = "time"
	end
	
	table.sort(APB.spottedListSort.keys, SORT_METHODS[APB.spottedListSort.method])
	
	SpottedList_Update()
end

function table.sizeOf(t)
	local count = 0
	for k, v in pairs(t) do count = count + 1 end
	return count
end
